#include "memory.h"

/**
 * Initializes a memory linked list
 * @return NULL list element pointer
 */
MEMORY *memoryCreate() {
    return NULL;
}

/**
 * Link a new element at the end of a circular list
 * @param memory pointer to first element in memory circular list
 * @param page pointer of the page to be inserted
 * @return true if the element linked or false if no memory allocated
 */
bool memoryInsert(MEMORY **memory, PAGE *page) {
    MEMORY *newMemory = (MEMORY*) malloc(sizeof (MEMORY));

    if (newMemory != NULL) {
        newMemory->page = page;

        MEMORY *iterator;
        iterator = *memory;

        // Check if the list is not empty
        if (!memoryEmpty(iterator)) {
            // Iterator will be the old last element
            iterator = (*memory)->previous;
            // New element will be the last in the circular list
            newMemory->previous = iterator;
            newMemory->next = *memory;
            iterator->next = newMemory;
            (*memory)->previous = newMemory;
        } else {
            // List was empty
            newMemory->previous = newMemory;
            newMemory->next = newMemory;
            *memory = newMemory;
        }
        page->address = newMemory;
        return true;
    } else {
        printf("ERROR memoryInsert: couldn't allocate memory\n");
        return false;
    }
}

/**
 * Checks if it's a empty list
 * @param memory pointer of the memory circular list to be tested
 * @return true if it is or false if it isn't
 */
bool memoryEmpty(MEMORY *memory) {
    if (memory == NULL) {
        return true;
    } else {
        return false;
    }
}

/**
 * Prints all elements of a linked list
 * @param memory pointer of the memory list to be printed
 */
void memoryPrint(MEMORY *memory) {
    MEMORY *iterator;
    iterator = memory;

    printf("\nMEMORY\n");
    if (iterator != NULL) {
        // Print all elements from the start
        do {
            printf("%d\n", iterator->page->number);
            iterator = iterator->next;
        } while (iterator != memory);
    }
}

/**
 * Push a memory element to the end of the list
 * @param memory pointer of the memory start position pointer
 * @param address pointer to the memory element to be pushed
 */
void updateMemoryPosition(MEMORY **memory, MEMORY *address) {
    MEMORY *previous;
    MEMORY *next;

    if (address == *memory) {
        // It's the first element (weird case)
        // Setting auxiliary
        *memory = address->next;
    } else {
        // It's any other element (normal case)
        // Setting auxiliary
        previous = address->previous;
        next = address->next;
        // Close the gap where we will remove the address
        previous->next = next;
        next->previous = previous;

        // Update the address at the end of the circular list
        ((*memory)->previous)->next = address;
        address->previous = (*memory)->previous;
        (*memory)->previous = address;
        address->next = *memory;
    }
}

/**
 * Find the number of elements of an existing memory structure
 * @param memory pointer to the memory start position
 * @return integer that represent the memory size
 */
int memorySize(MEMORY *memory) {
    // Is the memory empty?
    if (!memoryEmpty(memory)) {
        // Not empty
        MEMORY *iterator;
        iterator = memory;
        int i = 1;
        // Walk the memory once, counting it's elements
        while (iterator->next != memory) {
            iterator = iterator->next;
            i++;
        }
        return i;
    } else {
        // Empty
        return 0;
    }
}

/**
 * Remove an memory structure element from a memory circular list
 * @param memory pointer of the memory start position pointer
 * @param frame to be removed from the memory circular list
 */
void memoryRemove(MEMORY **memory, MEMORY *frame) {
    if (*memory == frame) {
        // It's the first page
        if (frame->next == frame) {
            // It's the only page
            *memory = NULL;
            // Update page status
            frame->page->address = NULL;
            free(frame);
        } else {
            // It's not the only page
            MEMORY *previous;
            MEMORY *next;
            previous = frame->previous;
            next = frame->next;
            previous->next = next;
            next->previous = previous;
            *memory = next;
            // Update page status
            frame->page->address = NULL;
            free(frame);
        }
    } else {
        MEMORY *previous;
        MEMORY *next;
        previous = frame->previous;
        next = frame->next;
        previous->next = next;
        next->previous = previous;
        // Update page status
        frame->page->address = NULL;
        free(frame);
    }
}

/**
 * Least recently used second chance algorithm to pick a victm page
 * @param memory pointer of the memory start position pointer
 * @param ramSize number of frames os the memory
 */
void lruSecondChance(MEMORY **memory, int ramSize) {
    MEMORY *iterator;
    iterator = *memory;
    // Counter for the movimentation of the iterator
    int i = 1;
    bool zeroZeroFlag;
    zeroZeroFlag = false;

    // Walk through the memory to find a victim
    while (true) {
        // Check reference and dirty bits
        if (!iterator->page->reference && !iterator->page->dirt) {
            // R = 0 and D = 0
            // Jackpot!
            iterator->page->replaces += 1;
            *memory = iterator->next;
            memoryRemove(memory, iterator);
            break;
        } else if (iterator->page->reference && !iterator->page->dirt) {
            // R = 1 and D = 0
            zeroZeroFlag = true;
            iterator->page->reference = false; // Become R = 0 and D = 0
        } else if (!iterator->page->reference && iterator->page->dirt) {
            // R = 0 and D = 1
            if (!zeroZeroFlag && i > ramSize) {
                // We'll have to remove a dirty one
                iterator->page->dirt = false;
                iterator->page->replaces += 1;
                *memory = iterator->next;
                memoryRemove(memory, iterator);
                break;
            }
        } else {
            // R = 1 and D = 1
            iterator->page->reference = false; // Become R = 0 and D = 1
        }
        iterator = iterator->next;
        i++;
    }
}

/**
 * Read a process page from memory
 * @param processList poiter of an active processes list
 * @param memory pointer of the memory start position pointer
 * @param ramSize number of frames os the memory
 * @param processId number of identification of the page's process
 * @param pageNumber number of the requested page
 * @return true if the page was readed or false if it wasn't
 */
bool memoryRead(PROCESS *processList, MEMORY **memory, int ramSize, int processId, int pageNumber) {
    PAGE *page;
    page = pageValid(processList, processId, pageNumber);
    // Check if page exists
    if (page != NULL) {
        // It's a valid page
        // Is it on memory already?
        if (page->address != NULL) {
            // It's on memory!
            // Let's update page status
            page->reference = true;
            page->accesses += 1;
            return true;
        } else {
            // Page exists but it's not on memory
            // Check if memory have space
            if (memorySize(*memory) < ramSize) {
                // Have space
                memoryInsert(memory, page);
                page->reference = true;
                page->accesses += 1;
                page->faults += 1;
                return true;
            } else {
                // Some page need to be removed
                lruSecondChance(memory, ramSize);
                memoryInsert(memory, page);
                page->reference = true;
                page->accesses += 1;
                page->faults += 1;
                return true;
            }
        }
    } else {
        printf("ERROR memoryRead: could not read the requested page\n");
        return false;
    }
}

/**
 * Write on a process page from memory
 * @param processList poiter of an active processes list
 * @param memory pointer of the memory start position pointer
 * @param ramSize number of frames os the memory
 * @param processId number of identification of the page's process
 * @param pageNumber number of the requested page
 * @return true if the page was readed or false if it wasn't
 */
bool memoryWrite(PROCESS *processList, MEMORY **memory, int ramSize, int processId, int pageNumber) {
    PAGE *page;
    page = pageValid(processList, processId, pageNumber);
    // Check if page exists
    if (page != NULL) {
        // It's a valid page
        // Is it on memory already?
        if (page->address != NULL) {
            // It's on memory!
            // Let's update page status
            page->reference = true;
            page->dirt = true;
            page->accesses += 1;
            return true;
        } else {
            // Page exists but it's not on memory
            // Check if memory have space
            if (memorySize(*memory) < ramSize) {
                // Have space
                memoryInsert(memory, page);
                page->reference = true;
                page->dirt = true;
                page->accesses += 1;
                page->faults += 1;
                return true;
            } else {
                // Some page need to be removed
                lruSecondChance(memory, ramSize);
                memoryInsert(memory, page);
                page->reference = true;
                page->dirt = true;
                page->accesses += 1;
                page->faults += 1;
                return true;
            }
        }
    } else {
        printf("ERROR memoryRead: could not read the requested page\n");
        return false;
    }
}

/**
 * Remove all pages of a process from the memory
 * @param processList poiter of an active processes list pointer
 * @param memory pointer of the memory start position pointer
 * @param processId number of identification of the process
 * @return pointer to the removed process or NULL if it doesn't exists
 */
PROCESS *procRemove(PROCESS **processList, MEMORY **memory, int processId) {
    PROCESS *iterator;
    iterator = *processList;

    // Find the process in the list
    while (!procListEmpty(iterator) && iterator->processId != processId) {
        iterator = iterator->next;
    }

    // Got the process?
    if (!procListEmpty(iterator) && iterator->processId == processId) {
        // Find and remove the pages from memory
        int i;
        for (i = 0; i < iterator->processSize; i++) {
            (iterator->pageTable + i)->reference = false;
            (iterator->pageTable + i)->dirt = false;
            if ((iterator->pageTable + i)->address != NULL) {
                memoryRemove(memory, (iterator->pageTable + i)->address);
            }
        }
        PROCESS *removedProcess;
        removedProcess = procListRemove(processList, processId);
        return removedProcess;
    }
    printf("ERROR procRemove: No process with this processId\n");
    return NULL;
}
